Skip to content

Raise the core tmux minimum to 3.3a - #21

Merged
tony merged 20 commits into
masterfrom
bump-min-tmux-000
Oct 20, 2026
Merged

tony merged 20 commits into
masterfrom
bump-min-tmux-000

Conversation

@tony

@tony tony commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Raise the minimum tmux for both modules to 3.3a.

The check lives in libtmux for .NET 0.0.0-alpha.22, which this PR pins. Before it sends any command, the package reads the connected daemon's version with a hook-free run-shell -C probe and refuses a daemon older than 3.3a, or one whose version it cannot read, with TmuxVersionTooLowException. Nothing changes on a refused daemon. Invoke-TmuxCommand and Invoke-TmuxChain are fenced on the checked version, so a daemon replaced by an older one after the check runs none of the batch. This layer reports the refusal in PowerShell terms and tests it end to end.

Changes

  • Pin LibTmux, LibTmux.Query.Json and LibTmux.Workspace to 0.0.0-alpha.22, regenerate both lock files, and build the CI and review packages from the v0.0.0-alpha.22 commit.
  • Report a refusal as ErrorCategory.NotImplemented, also when a workspace build failure wraps it. The package's remedy names a NuGet release, so the error details name the module a PowerShell user can install instead: LibTmux 0.1.0-alpha2 from the PowerShell Gallery.
  • A refusal is a non-terminating error like any other operation failure, so it follows -ErrorAction. The compatibility page says to use -ErrorAction Stop, and that Invoke-TmuxCommand kill-server on an endpoint with no daemon is a no-op.
  • Two behaviours arrive with alpha.22 and are documented and tested:
    • Wait-TmuxPaneText attaches no control client below tmux 3.7. It polls without -AllowPollingFallback and reports PollingFallback.
    • On tmux 3.7 and newer, commands from one server handle share a control client. A blocking command nested in a list, such as if-shell -F 1 'wait-for release', holds the commands behind it; a top-level wait-for still runs on its own. LIBTMUX_CONTROL_TRANSPORT=0 sends each command from its own process.
  • README, compatibility page, contributing guide, MCP guide and help name the 3.3a floor and the releases CI covers. The README links the workspace action kinds it names, so the docs site resolves them.

Tests

tests/VersionCheck.Tests.ps1 (eng/Test.ps1 -Suite VersionCheck) runs against the tmux first on PATH:

  • Below 3.3a, reads, writes, control attaches, waits, raw commands and chains are refused as NotImplemented with the module remedy, on a live daemon and on an absent endpoint, and the daemon's sessions, options, environment and the absent socket are left unchanged. A client wrapper that claims tmux 9.9 does not get past the check.
  • A daemon replaced after the check fails the fence for both batch cmdlets and leaves a sentinel session uncreated. A supported replacement is checked again before the command runs.
  • On 3.3a and newer, neither the check nor the fence fires an after-display-message or command-error hook, with native commands as positive controls. The command-error half is reported SKIPPED before tmux 3.5, where set-hook rejects it.
  • The version grammar: 3.2a, 3.3, next-3.3, 3.3-rc, master and malformed text are refused; 3.3a, 3.4, 3.4-rc, next-3.4 and 3.7d are accepted.
  • Daemons older than 3.2 are refused by their release, with a bounded read even when the daemon keeps stdout open past the client's exit.

Existing suites follow the new pin: stand-in transports answer the probe, tracing wrappers skip the probe, scripts that wrap tmux run with LIBTMUX_CONTROL_TRANSPORT=0, the source query suite expects the planner's refusal on a tmux that is not source-verified, and the 3.2a window-size workaround is gone.

CI

  • Linux: tmux 3.3a, 3.4, 3.5, 3.6, 3.7a, 3.7b, 3.7c, 3.8 and master on PowerShell 7.4.20 and 7.6.6.
  • reject: tmux 3.2a and unpatched 3.3, built from pinned commits, run the VersionCheck suite against the packed modules. reject-old-daemon runs a 3.0a or 3.1c daemon behind a 3.3a client. LIBTMUX_REQUIRE_REJECTED fails a lane whose tmux is not the release it names. Both jobs join the gate and the release artifact binding.
  • The advisory macOS trial runs the VersionCheck suite first and builds tmux with Homebrew's utf8proc.

The docs build check is red because the pinned docs publisher predates the alpha.22 package hash; it clears with the docs site update.

Closes #17.

@tony
tony deployed to docs-preview October 17, 2026 09:36 — with GitHub Actions Active
@tony
tony deployed to docs-preview October 17, 2026 09:54 — with GitHub Actions Active
@tony
tony deployed to docs-preview October 17, 2026 11:21 — with GitHub Actions Active
tony added 9 commits October 17, 2026 06:44
why: Lanes below the floor must assert refusal without running suites
that need a supported daemon.

what:
- Add `eng/Test.ps1 -Suite VersionCheck`, which installs the packed
  modules and runs tests/VersionCheck.Tests.ps1 alone.
- Run the suite with the others in the Product suite.
why: tmux 3.2a sized detached windows from the command client unless
`window-size` was manual, so the create and guide fixtures forced that
option on that release only. The 3.3a floor removes the release.

what:
- Remove the forced option from the create and guide fixtures and the
  note from the create guide.
- Keep reading the daemon version in the guide fixture for its deferral
  check and in Create.Tests.ps1 for tmux 3.4's dollar-sign escaping.
why: The workflow still exercised 3.2a, which the module no longer
supports, and had no lane proving that older daemons are refused.

what:
- Pair PowerShell 7.4.20 and 7.6.6 with tmux 3.3a and 3.7c, and swap
  3.2a for the same 3.3a source commit in the advisory macOS trial. The
  MCP discovery step follows the lower endpoint.
- Add a `reject` job that builds tmux 3.2a and unpatched 3.3 from their
  pinned source commits, checks the tag, and runs only the VersionCheck
  suite against the packed modules. The 3.3 commit is the `3.3` tag of
  tmux/tmux; the shared build script does not know that release, so the
  job builds from source directly.
- Add the job to the aggregate gate and the release artifact binding, so
  a release needs the rejection cells as well as the supported ones. The
  artifact checker's job set and its unit test follow.
- Add a `reject-old-daemon` job that builds tmux 3.3a and a 3.0a or
  3.1c daemon from their pinned tag commits and runs the VersionCheck
  suite with the 3.3a client against that daemon. The job joins the
  gate and the release artifact binding.
why: The install note, the compatibility page and the contributing guide
disagreed about the minimum tmux.

what:
- State the 3.3a minimum, how the connected daemon is checked, and where
  it is tested in the README, the compatibility page and the
  contributing guide.
- Explain on the compatibility page that the linked .NET package checks
  the daemon before it sends anything, that operations fail with
  `TmuxVersionTooLowException` instead of returning nothing, what the
  command guard does when a daemon is replaced, and that the control
  cmdlets need no check of their own.
- Prepare a 3.3a plan in the Get-TmuxQueryPlan example; regenerate the
  help XML and follow in the help-example and source query tests.
- Describe how daemons older than 3.2 are refused and which CI job
  proves it.
why: Several suites wrap tmux in a script that logs one line per
invocation, then assert that invalid input, previews and offline paths
dispatch nothing. The version check now runs one hook-free probe, or an
executable `-V` for an absent endpoint, ahead of the library call that
rejects the input.

what:
- Make those wrappers skip exactly the probe and `-V` forms and keep
  counting every other invocation, including the fenced batch.
why: tmux 3.3a's configure stops with "must give --enable-utf8proc or
--disable-utf8proc" when it finds utf8proc, which Homebrew installs, so
the shared build script's bare configure failed every 3.3a cell before
any suite ran.

what:
- Configure the already cloned and verified source in the trial with
  `--enable-utf8proc`.
- Write the same source-commit marker the later steps read, and keep the
  3.7c cells on the same path.
why: With `--enable-utf8proc` the arm64 cells stopped at "utf8proc not
found": Homebrew installs under /opt/homebrew, outside the compiler's
default search paths, while the Intel cells find it under /usr/local.

what:
- Pass the include and library directories from `brew --prefix utf8proc`
  to configure.
why: Readers could not tell how a refusal behaves under `-ErrorAction`,
whether the MCP server is covered, or why the benchmark receipts name
3.2a.

what:
- Say on the compatibility page that a refusal is a non-terminating
  error that follows `-ErrorAction`, so `SilentlyContinue` hides it as
  it does any operation failure.
- Say that `kill-server` on an absent endpoint is a no-op, and that the
  separate MCP tool is built on the same package and refuses the same
  daemons.
- Note in the benchmark README that its 3.2a receipts predate the floor.
why: The Product suite stops at its first failing worker, and on macOS
that is ResourceInstall.Tests.ps1, so the version check suite never ran
there.

what:
- Run `-Suite VersionCheck` first in the advisory trial and report it
  beside Product and Documentation in the summary and warning.
- Keep `continue-on-error` on every step: the trial stays advisory.
@tony
tony force-pushed the bump-min-tmux-000 branch from d9994b9 to 8785b3e Compare October 17, 2026 12:16
tony added 2 commits October 17, 2026 10:50
why: The matrix covered only the floor and the newest release, so a
release in between or an upcoming one could break unnoticed.

what:
- Return the Linux matrix to 3.3a, 3.4, 3.5, 3.6, 3.7a, 3.7b and 3.7c,
  each on PowerShell 7.4.20 and 7.6.6, and add a 3.8 lane (tag and
  commit pinned) and a master lane (`next-3.9`, built from the current
  head, logging the commit it built). The shared build script knows
  neither, so the job builds them from source.
- Export `LIBTMUX_REQUIRE_REJECTED` in the rejection job so a lane whose
  tmux is not the unsupported release it names fails instead of passing
  quietly.
- Follow with the release-artifact job set and its unit test.
why: The compatibility page and the contributing guide listed an
outdated matrix.

what:
- List the releases the Linux workflow now exercises.
@tony
tony force-pushed the bump-min-tmux-000 branch 2 times, most recently from a0da4d8 to bf018d3 Compare October 17, 2026 17:19
@tony
tony force-pushed the bump-min-tmux-000 branch from bf018d3 to 3a7f24e Compare October 17, 2026 18:53
why: Callers need to know that older tmux daemons are now refused, what
the refusal looks like, and two behaviours that change with the package:
pane text waits poll below tmux 3.7, and commands share a control client
on 3.7 and newer.

what:
- Add a breaking-change entry for the 3.3a floor, the version-too-low
  error on reads, the replaced-daemon refusal, the absent-endpoint
  kill-server no-op and the libtmux for .NET 0.0.0-alpha.22 package
  that performs the check.
- Add entries for polling below 3.7 and for the shared control client,
  with the limit on blocking commands nested in a list and the
  LIBTMUX_CONTROL_TRANSPORT=0 opt-out.
@tony
tony force-pushed the bump-min-tmux-000 branch from 3a7f24e to 831c283 Compare October 17, 2026 19:04
@tony
tony deployed to docs-preview October 18, 2026 02:29 — with GitHub Actions Active
@tony
tony force-pushed the bump-min-tmux-000 branch from 831c283 to 2c5c712 Compare October 18, 2026 04:05
@tony
tony deployed to docs-preview October 18, 2026 05:59 — with GitHub Actions Active
@tony
tony force-pushed the bump-min-tmux-000 branch from 2c5c712 to 84153c7 Compare October 20, 2026 01:57
tony added 4 commits October 19, 2026 21:34
why: The 3.3a floor relies on the package refusing an older daemon
before it sends a command, and alpha.20 does not.

what:
- Pin LibTmux, LibTmux.Query.Json and LibTmux.Workspace to
  0.0.0-alpha.22 and regenerate both lock files.
- Build the CI and review packages from the v0.0.0-alpha.22 commit.
- Move the install, MCP and manifest release notes to alpha.22.
why: On tmux 3.7 and newer alpha.22 sends one-shot reads over a shared
control client. Scripts that wrap tmux to trace or inject invocations
saw none of them and failed with the new pin. The other scripts do not
depend on seeing each command and run on the default transport.

what:
- Set LIBTMUX_CONTROL_TRANSPORT=0 in eng/Test.ps1 only for the Snapshot,
  Attachment, SourceQuery, WorkspaceApply, WorkspaceCorpus, Help and
  GuideExamples scripts, which wrap tmux.
- Say so in CONTRIBUTING.
why: alpha.22 attaches no control client for a pane text wait below tmux
3.7. The wait polls without -AllowPollingFallback and reports
PollingFallback, where alpha.20 refused to poll without the opt-in. The
checks that expected no polling failed on tmux 3.3a through 3.6.

what:
- Add Test-PollingExpected, which reads the tmux banner, and expect
  PollingFallback below 3.7 in the pane text wait, README, guide,
  help-example and MCP discovery checks.
- Say in the input guide, the Wait-TmuxPaneText help and the MCP guide
  that older releases always poll, and regenerate the help XML.
- Say in the MCP guide that waits require control observation on tmux
  3.7 and newer.
why: alpha.22 checks the daemon before it sends a command, so a stand-in
transport meets a version probe and a guarded command list, and the
wait-context probe failed with "the wait channel could not be
withdrawn". The package also verifies exact source evaluation only for
stable releases up to 3.8, so a later patch release or a development
build refuses the exact plan the source query suite required.

what:
- Answer the probe and unwrap the command guard in the wait-context
  probe's stand-in transport.
- In the source query suite, expect the planner's refusal and
  residual-only plans on a tmux that is not source-verified, and run
  every other check there.
tony added 3 commits October 19, 2026 21:34
why: On tmux 3.7 and newer commands from one server handle share a
control client. A blocking command nested in a list, such as
`if-shell -F 1 'wait-for release'`, is not seen and holds the commands
behind it, so the concurrent-command check stalled on the default
transport.

what:
- Document the limit in the commands guide, that a top-level `wait-for`
  stays concurrent, and the LIBTMUX_CONTROL_TRANSPORT=0 opt-out.
- Block on a top-level `wait-for` in the concurrent-command check.
why: A cmdlet that surfaced the package's TmuxVersionTooLowException
reported it as an invalid operation, like any failed command, so a
script filtering on the error category could not tell an unsupported
tmux from a failure. The package's remedy names LibTmux 0.0.0-alpha.21,
which is not a module a PowerShell user can install.

what:
- Report TmuxVersionTooLowException as ErrorCategory.NotImplemented.
- Replace the package's remedy in the error details with the release a
  PowerShell user can install, LibTmux 0.1.0-alpha2 from the PowerShell
  Gallery, and append it if the package text changes.
- Look through inner exceptions for the refusal, so one wrapped by a
  workspace build failure gets the same category and details.
why: The package checks the daemon's version before it sends a command.
Nothing here fails when a cmdlet stops reaching that check, a refused
command takes effect, or the check starts firing tmux hooks.

what:
- Add tests/VersionCheck.Tests.ps1, which runs against the tmux first on
  PATH.
- Below 3.3a, assert that session, client, hook and snapshot reads,
  Connect-TmuxControl, New-TmuxSession, Set-TmuxOption,
  Set-TmuxEnvironment, Wait-TmuxChannel, raw commands and chains are
  refused on a live daemon and on an absent endpoint, including through
  a client wrapper that claims `tmux 9.9`, and that the daemon's
  sessions, option, environment and the absent socket are unchanged.
  Every operation must be refused as NotImplemented with a remedy that
  names LibTmux 0.1.0-alpha2. Discovery operations on an absent
  endpoint, which start nothing, must report the missing socket in a
  TmuxCommandException; a read on a supported executable reports the
  same.
- Capture a pane and a workspace plan through a double that claims a
  supported tmux, then replace the daemon with one below the floor and
  assert that Wait-TmuxPaneText and Invoke-TmuxWorkspace are refused as
  NotImplemented with the remedy and that the host script never runs.
- Check the remedy rewrite directly, for the package's wording and for
  a reworded message that gets the release appended.
- On 3.3a or newer, assert that reads and writes are checked and that
  neither the check nor the fence fires an after-display-message or
  command-error hook, measured on a handle not yet checked so the probe
  itself runs, with a native display-message and a failing command as
  positive controls. The command-error half is reported
  SKIPPED before tmux 3.5, where `set-hook` rejects it.
- Assert that a replaced daemon fails the fence for both batch cmdlets
  and leaves a sentinel session uncreated, that a replacement below the
  floor is refused, that a supported replacement is checked again
  before the command runs, and that an unchanged daemon behind the same
  wrapper is not refused.
- Assert the version grammar: 3.2a, 3.3, next-3.3, 3.3-rc, master and
  malformed text are refused; 3.3a, 3.4, 3.4-rc, next-3.4 and 3.7d are
  accepted.
- Assert that one server handle probes once, that a daemon gone after
  the check is checked again before the command is sent again, and that
  a renamed executable still starts a daemon.
- On tmux 3.7 or newer, assert that attaching and commanding through
  the control cmdlets fires no more hooks than the package's own attach.
- Answer the probe with doubles that print the 3.1c usage line, the 3.0a
  protocol refusal and the unknown `-N` error, and require each refusal
  to name its release and the remedy. The 3.1c and 3.0a doubles keep
  stdout open past the client's exit, as those daemons do, and the read
  must fail within 10 s. With LIBTMUX_OLD_DAEMON set, the same bound
  applies to a real 3.0a or 3.1c daemon behind a 3.3a client.
@tony
tony force-pushed the bump-min-tmux-000 branch 2 times, most recently from 0ebbaa2 to 00a482f Compare October 20, 2026 03:05
why: The docs site resolves every code mention in a guide to an API page
and fails the PowerShell guides on any it cannot resolve. The README named
`CreateSession` and `CaptureResult` without links, and set `PaneCount`, a
calculated property of the example's own Select-Object, as code, which no
API page can resolve.

what:
- Link `CreateSession` and `CaptureResult` to their
  LibTmux.Workspace.WorkspaceActionKind pages, as docs/workspace.md
  already does for `CaptureResult`
- Say "a pane count of 2" in prose instead of a `PaneCount` code span
@tony
tony marked this pull request as ready for review October 20, 2026 21:41
@tony
tony merged commit 2cd32f9 into master Oct 20, 2026
36 of 37 checks passed
@tony
tony deployed to docs-preview-cleanup October 20, 2026 21:42 — with GitHub Actions Active

This branch was successfully deployed

2 active (1 outdated) deployments
docs-preview-cleanup — 48d77050 Deployed Oct 20, 2026 by tony via cleanup #12
docs-preview — 2c5c7122 Deployed Oct 18, 2026 by tony via publish (pr-21, pr, false, true, powershell) / publish #39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Require tmux 3.3a for core operations

1 participant